package com.taldh.utils.collection;

import com.google.common.collect.Lists;
import org.apache.commons.collections.CollectionUtils;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author Yuan.Donghao
 * @date 2019年3月18日
 */
public final class ListBeanMap {

    private ListBeanMap() {
        //Don't let anyone instantiate this class
        throw new RuntimeException("This class cannot be constructed!");
    }

    /**
     * 复杂类型领域模型，一对多映射
     * @param parents  父节点
     * @param children 子节点
     * @param parentId 父节点对应子节点的字段
     * @param childId  子节点对应父节点的字段
     * @param action   父节点的set方法，将子节点对应的数据set到父节点
     * @param <T>      父节点的类型
     * @param <K>      子节点的类型
     * @param <U>      父子节点使用何种类型来做对应
     * @return         返回值只是为了在return的时候比较方便，其实在做映射的时候，对应的值已经set到父节点之中。
     */
    public static <T,K,U> List<T> one2Many(List<T> parents, List<K> children,
                                           Function<? super T, ? extends U> parentId,
                                           Function<? super K, ? extends U> childId,
                                           BiConsumer<? super T,? super List<K>> action) {
        if (CollectionUtils.isEmpty(parents) || CollectionUtils.isEmpty(children)) {
            return new ArrayList<>();
        }
        Map<U, T> pMap = parents.stream().collect(Collectors.toMap(parentId, p -> p));
        Map<U, List<K>> cMap = children.stream().collect(Collectors.toMap(childId,
                Lists::newArrayList,
                (List<K> oldList, List<K> newList) -> {
            oldList.addAll(newList);
            return oldList;
        }));
        pMap.forEach((k,v) -> action.accept(v, cMap.get(k)));
        return parents;
    }

    /**
     * 复杂类型领域模型，一对一映射
     * @param parents  父节点
     * @param children 子节点
     * @param parentId 父节点对应子节点的字段
     * @param childId  子节点对应父节点的字段
     * @param action   父节点的set方法，将子节点对应的数据set到父节点
     * @param <T>      父节点的类型
     * @param <K>      子节点的类型
     * @param <U>      父子节点使用何种类型来做对应
     * @return         返回值只是为了在return的时候比较方便，其实在做映射的时候，对应的值已经set到父节点之中。
     */
    public static <T,K,U> List<T> one2One(List<T> parents, List<K> children,
                                          Function<? super T, ? extends U> parentId,
                                          Function<? super K, ? extends U> childId,
                                          BiConsumer<? super T,? super K> action) {
        if (CollectionUtils.isEmpty(parents) || CollectionUtils.isEmpty(children)) {
            return new ArrayList<>();
        }
        Map<U, T> pMap = parents.stream().collect(Collectors.toMap(parentId, p -> p));
        Map<U, K> cMap = children.stream().collect(Collectors.toMap(childId, c -> c));
        pMap.forEach((k,v) -> action.accept(v, cMap.get(k)));
        return parents;
    }

    /**
     * Map 类型追加工具
     * @param targets 目标Map
     * @param sources 要追加的map
     * @param targetKey 目标map的通过哪个key追加, 如果出现脏数据，将删除
     * @param sourceKey 源map的通过哪个key追加
     * @param isRemoveNotMapping 是否删除脏数据
     */
    public static void appendMapOne2OneIsRemove(List<Map<String, Object>> targets, List<Map<String, Object>> sources,
                                                String targetKey, String sourceKey, boolean isRemoveNotMapping) {
        appendMapOne2One(targets, sources, targetKey, sourceKey, null, isRemoveNotMapping);
    }

    /**
     * Map 类型追加工具
     * @param targets 目标Map
     * @param sources 要追加的map
     * @param targetKey 目标map的通过哪个key追加, 如果出现脏数据，将删除
     * @param sourceKey 源map的通过哪个key追加
     * @param specialLogic 特殊逻辑，在过滤之后执行
     * @param isRemoveNotMapping 当没有找出对应的source时是否删调对应的target
     */
    public static void appendMapOne2One(List<Map<String, Object>> targets, List<Map<String, Object>> sources,
                                        String targetKey, String sourceKey,
                                        BiConsumer<Map, List<Map<String, Object>>> specialLogic, boolean isRemoveNotMapping) {
        if (CollectionUtils.isEmpty(targets) || CollectionUtils.isEmpty(sources)) {
            return;
        }

        Iterator<Map<String, Object>> iterator = targets.iterator();

        while (iterator.hasNext()) {
            Map<String, Object> target = iterator.next();
            if (!target.containsKey(targetKey)) {
                continue;
            }
            List<Map<String, Object>> collect =
                    sources.stream().parallel().filter(source -> source.get(sourceKey).equals(target.get(targetKey))).collect(Collectors.toList());
            if (specialLogic != null) {
                specialLogic.accept(target, collect);
            }
            if (CollectionUtils.isEmpty(collect) || collect.size() > 1) {
                if (isRemoveNotMapping) {
                    iterator.remove();
                }
                continue;
            }
            Map<String, Object> sourceMapped = collect.get(0);
            target.putAll(sourceMapped);
        }
    }


    /**
     * 收集ID
     * @param ts LIST 集合
     * @param function 方法
     * @param <T> 收集的类型
     * @return 收集的ids
     */
    public static <T> String collectIds(List<T> ts, Function<T, Long> function) {
        if (CollectionUtils.isEmpty(ts)) {
            return null;
        }
        return ts.stream().parallel().filter(t -> Objects.nonNull(function.apply(t))).map(function).collect(Collectors.toList())
                .stream().map(Object::toString).collect(Collectors.joining(","));
    }

    /**
     * 跟进id收集对象
     * @param ts 列表
     * @param function 方法
     * @param <K> key
     * @param <T> 列表类型
     * @return 收集结果成一个map容器
     */
    public static <K, T> Map<K, T> collectObjsByKey(List<T> ts, Function<T,K> function) {
        if (CollectionUtils.isEmpty(ts)) {
            return null;
        }
        return ts.parallelStream().collect(Collectors.toMap(function, a -> a));
    }

    /**
     * 获取接口的泛型，没有就返回null
     * @return 接口的泛型
     */
    public Class<?> getGenericClass() {
        Type t = getClass().getGenericSuperclass();
        if (t instanceof ParameterizedType) {
            Type[] types = ((ParameterizedType) t).getActualTypeArguments();
            return (Class<?>) types[0];
        }
        return null;
    }
}